from abc import abstractmethod
from tqdm import tqdm
import geopandas as gpd
import shapely
import numpy as np
import pandas as pd
import settings
from merge.utils.Method_JoinMatchWell import *
from merge.utils.MethodJoinCut import *
from  settings import *

class DataSetProvider:
    """数据集类
    用途：保存融合过程所需上报路网数据、匹配后的遥感路网数据；提供合并类型分类、融合路段集检测、路段融合等方法
    方法

    Raises:
        NotImplementedError: [description]
        NotImplementedError: [description]
        NotImplementedError: [description]

    """
    match_result = None
    rp_df = None
    layer = None

    def __init__(self,
                 rs_layer,
                 rp_layer,
                 Envelope,
                 THRESHOLD_MATCH_LENGTH,
                 THRESHOLD_EXTEND_TOLERANCE,
                 THRESHOLD_BUFFER_COVER,
                 SRID,
                 result_path=result_path, 
                 check_path=check_path):
        """初始化数据集对象

        Args:
            THRESHOLD_MATCH_LENGTH (float): 匹配长度比例阈值，描述遥感路段与上报路段匹配长度比值，划分第一匹配条件与第二匹配条件，目前两条件融合方法已经合并，参数可弃用
            THRESHOLD_EXTEND_TOLERANCE ([type]): 捕捉影响长度因子，描述因捕捉到端点这一事件对整条路段长度的影响比例，仅在路段长度小于最大影响距离时起作用
            THRESHOLD_BUFFER_COVER ([type]): 判断裁剪后的路段与上报路网相似程度阈值的因子
            SRID ([type]): 坐标系定义
            save (bool), optional): 保存中间融合过程图像. Defaults to False.
            layer ([type], optional): 上报路网图层名. Defaults to None.
        """
        self.THRESHOLD_MATCH_LENGTH = THRESHOLD_MATCH_LENGTH
        self.THRESHOLD_EXTEND_TOLERANCE = THRESHOLD_EXTEND_TOLERANCE
        self.THRESHOLD_BUFFER_COVER = THRESHOLD_BUFFER_COVER
        self.SRID = SRID
        self.rs_layer = rs_layer
        self.rp_layer = rp_layer
        self.Envelope = Envelope
        self.result_path = result_path
        self.check_path = check_path
        self.ldbmlist = self.read_rs_road(rs_layer)
        self.read_rp_road(rp_layer, self.ldbmlist)
        self.checkDataSet()
        
        ## 检查结果路径是否存在
        if not os.path.exists(image_path):
                os.makedirs(image_path)
        if( not os.path.exists(self.result_path)):
            os.makedirs(self.result_path)
        if ( not os.path.exists(self.check_path)):
            os.makedirs(self.check_path)
        pass

    def read_rs_road(self, rs_layer):
        """
        使用POSTGIS数据库连接获取遥感路网数据图层

        Args:
            src_layer_name (str): 匹配后的遥感路网图层名

        Raises:
            ConnectionError: 数据库连接错误
        """
        try:
            if self.Envelope is not None:
                sql = f"SELECT * FROM {rs_layer.name} WHERE st_intersects(geom, ST_GeomFromText('{self.Envelope.wkt}',{settings.SRID}))"
            else:
                sql = f"SELECT * FROM {rs_layer.name}"
            self.match_result = gpd.read_postgis(con=settings.conn, sql=sql)
        except ConnectionError as conn_error:
            raise ConnectionError(
                "RemoteSensingLayerConnetctionError: Could not read database, please check the layer name or the database connection \n detail:"
                + str(conn_error))
        except Exception as e:
            raise Exception("未知错误")

        self.match_result["NID"] = [
            str(i) for i in range(len(self.match_result))
        ]
        ## 列名全小写
        self.match_result.columns = [
            str.lower(column) for column in self.match_result.columns
        ]
        # * 将常用的几个字段设置为字符串
        self.match_result[self.rs_layer.match_road] = np.array(
            self.match_result[self.rs_layer.match_road], dtype=str)
        return list(self.match_result[self.rs_layer.match_road])

    def read_rp_road(self, rp_layer, ldbmlist: list):
        """读取上报路网数据

        Args:
            layer_type (str): 上报路网名
        """
        ## 读取上报路网
        try:
            self.rp_df = gpd.read_postgis(
                con=settings.conn,
                sql=
                f"select * from {rp_layer.name} where gid in ({str(ldbmlist)[1:-1]}) "
            )
            self.rp_df[self.rp_layer.pk] = np.array(
                self.rp_df[self.rp_layer.pk], dtype=str)
            pass
        except:
            raise ConnectionError(
                "Could not read database, please check the layer name or the database connection"
            )

    # 补充切分后路段桩号的移交，目前暂未使用
    # def _get_interpolate_stake(self, post=False) -> gpd.GeoDataFrame:
    #     gdf_split = copy.deepcopy(self.rp_df)
    #     gdf_split = gdf_split.set_index(self.rp_df.gid)
    #     gdf = gpd.read_postgis(
    #         con=settings.conn,
    #         sql=f"select * from {origin_layer_option}{self.layer}")

    #     for i in tqdm(range(len(gdf_split))):
    #         geo = gdf_split.iloc[i]
    #         ldbm = geo.ldbm
    #         geo_origin = gdf[gdf.ldbm == ldbm]
    #         line_origin = geo_origin.geom.values[0]
    #         line = geo.geom

    #         gid = geo.name
    #         start_poi = shapely.geometry.Point(line.coords[0])
    #         end_poi = shapely.geometry.Point(line.coords[-1])
    #         start_inter_length = line_origin.project(start_poi)
    #         end_inter_length = line_origin.project(end_poi)
    #         if (start_inter_length == 0
    #                 or abs(start_inter_length - line_origin.length) < 1e-9):
    #             # 是起点，可以把东西移动上去
    #             gdf_split.loc[gid, 'qdmc'] = geo_origin.qdmc.values[0]
    #             start_inter_length = 0
    #             pass
    #         if (end_inter_length == line_origin.length
    #                 or abs(end_inter_length - line_origin.length) < 1e-9):
    #             end_inter_length = line_origin.length
    #             gdf_split.loc[gid, 'zdmc'] = geo_origin.zdmc.values[0]
    #             # 是终点，把属性移动上去
    #             pass
    #         start_inter_stake = np.round(
    #             start_inter_length / line_origin.length *
    #             (geo_origin.zdzh.values[0] - geo_origin.qdzh.values[0]) +
    #             geo_origin.qdzh.values[0], 3)
    #         end_inter_stake = np.round(
    #             end_inter_length / line_origin.length *
    #             (geo_origin.zdzh.values[0] - geo_origin.qdzh.values[0]) +
    #             geo_origin.qdzh.values[0], 3)
    #         gdf_split.loc[gid, 'qdzh'] = start_inter_stake
    #         gdf_split.loc[gid, 'zdzh'] = end_inter_stake

    #     gdf_split['ldbm_reset'] = list(gdf_split.index)
    #     if (post):
    #         # 需要上传到数据库
    #         raise NotImplementedError
    #         pass
    #     return gdf_split

    def checkDataSet(self):
        """检查上报路网与遥感路网是否对应，并根据路段对应关系划分处理方法
        """

        ## 获取待融合编号
        rp_c_ldbm_list = list(
            set(list(self.match_result[self.rs_layer.match_road])))
        ## 合并测试
        joinTag = []
        length_simTag = []
        print("正在检查合并情况...")
        for i, ldbm in tqdm(enumerate(rp_c_ldbm_list)):
            match_ld_part_df = self.match_result[self.match_result[
                self.rs_layer.match_road] == ldbm]
            joinLine = shapely.ops.linemerge(list(match_ld_part_df.geometry))
            if type(joinLine) is shapely.geometry.LineString:
                joinTag.append(1)
            else:
                joinTag.append(0)
                
            ## ? 如果上报路网开Envelope的范围不够大，可能会有空DataFrame的错误
            length_simTag.append(joinLine.length / self.rp_df[self.rp_df[
                self.rp_layer.pk] == ldbm].iloc[0][self.rp_layer.geom].length)

        tdf = pd.DataFrame([rp_c_ldbm_list, joinTag, length_simTag]).T
        tdf.columns = ["ldbm", "joinTag", "lengthPer"]

        ## 划分数据集
        ### * 第一条件
        x = tdf.joinTag == 1
        y = tdf.lengthPer > self.THRESHOLD_MATCH_LENGTH
        CONDITION_1 = np.logical_and(x, y)

        ### * 第二条件
        x = tdf.joinTag == 1
        y = tdf.lengthPer <= self.THRESHOLD_MATCH_LENGTH
        CONDITION_2 = np.logical_and(x, y)

        ### * 第三条件
        x = tdf.joinTag == 0
        CONDITION_3 = x

        self.LDBM_GROUP_1 = list(tdf[CONDITION_1].ldbm)
        self.LDBM_GROUP_2 = list(tdf[CONDITION_2].ldbm) + self.LDBM_GROUP_1
        self.LDBM_GROUP_3 = list(tdf[CONDITION_3].ldbm)
        df = self.match_result.query("match_road in @self.LDBM_GROUP_3")
        df2 = self.rp_df.query("ldbm in @self.LDBM_GROUP_3")
        print(
            f"本图层共有上报路网{len(self.rp_df)}条\n其中被匹配的上报路网有{len(rp_c_ldbm_list)}条\n遥感路网{len(self.match_result)}条\n"
        )
        print(
            f"条件1：\t{len(self.LDBM_GROUP_1)}条\n条件2：\t{len(self.LDBM_GROUP_2)}条\n条件3：\t{len(self.LDBM_GROUP_3)}条\n"
        )
        ## TODO 数据检查
        pass

    def join(self):
        """
        路网融合方法
        """
        if (len(self.LDBM_GROUP_2) == 0 and len(self.LDBM_GROUP_3) == 0):
            print("本图层无可合并路段，请检查输入数据！")
            self.JoinResult = None
            return

        [
            self.JoinResult_Group3_gdf, self.JoinResult_Group2_gdf,
            self.JoinResult
        ] = [None] * 3

        ## * CONDITION_2
        ### * 创建处理工具对象，指定参数，设置环境
        TOOL_JOIN_CUT = MethodJoinCut(
            self,
            THRESHOLD_BUFFER_COVER=self.THRESHOLD_BUFFER_COVER,
            THRESHOLD_TORLERANCE=self.THRESHOLD_EXTEND_TOLERANCE,
        )

        ### * 对满足条件2的路段进行融合
        if (len(self.LDBM_GROUP_2) != 0):
            self.JoinResult_Group2_gdf, self.JoinStatus_Group2_df = TOOL_JOIN_CUT.joinMatchCutWorkflow_ldbmList(
                self.LDBM_GROUP_2,
                joinMethod=MethodjoinCutThresholdSettings.snap_method)

        ## * CONDITION_3

        ### * 对满足条件3的路段进行融合
        if (len(self.LDBM_GROUP_3) != 0):
            self.JoinResult_Group3_gdf, self.JoinStatus_Group3_df = TOOL_JOIN_CUT.joinMatchDisconnectWorkflow_ldbmList(
                self.LDBM_GROUP_3,
                joinMethod=MethodjoinCutThresholdSettings.snap_method,
                THRESHOLD_SHORTITEM_CLEAR_LENGTH=
                MethodjoinCutThresholdSettings.
                THRESHOLD_SHORTITEM_CLEAR_LENGTH,
                THRESHOLD_SHORTITEM_CLEAR_COORDS_NUM=
                MethodjoinCutThresholdSettings.
                THRESHOLD_SHORTITEM_CLEAR_COORDS_NUM)

        ### * 合并条件2与条件3数据
        if (self.JoinResult_Group2_gdf is not None):
            if self.JoinResult_Group3_gdf is not None:
                JoinResult_temp = self.JoinResult_Group2_gdf.append(
                    self.JoinResult_Group3_gdf)
            else:
                JoinResult_temp = self.JoinResult_Group2_gdf
        else:
            JoinResult_temp = self.JoinResult_Group3_gdf

        rp_df_order_by_ldbm = self.rp_df.set_index(self.rp_layer.pk)
        rp_df_order_by_ldbm.loc[JoinResult_temp.ldbm,
                                'geom'] = JoinResult_temp.geom.values
        self.JoinResult = rp_df_order_by_ldbm
        self.JoinResult[self.rp_layer.pk] = list(self.JoinResult.index)
        self.JoinResult = self.JoinResult.reset_index(drop=True)
        self.JoinResult.set_crs(epsg=settings.SRID, inplace=True)
        pass

    # -----------------------------------------------------------00:33
    def densify(self):
        raise NotImplementedError

    def Post_JoinResult_To_Database(self,
                                    src_table_name,
                                    target_table_name,
                                    columns,
                                    con=None,
                                    if_exists='replace'):
        if con is None:
            print("无数据连接，请检查！")
            return

        sql = f"""drop table if exists {target_table_name};
        select *  into {target_table_name} from {src_table_name} 
        """
        #where st_intersects(geom, ST_GeomFromText('{self.Envelope.wkt}')) 
        conn.cursor().execute(sql)
        conn.commit()

        # ldbms = list(self.rp_df[self.rp_layer.segment_code])
        sql = f"select * from {self.rp_layer.name}"
        rp_data_df = gpd.read_postgis(con=settings.conn, sql=sql)
        rp_data_df[self.rp_layer.pk] = np.array(rp_data_df[self.rp_layer.pk],
                                                dtype=str)
        rp_data_df.set_index(self.rp_layer.pk, inplace=True)

        for i in range(len(self.JoinResult)):
            df_line = self.JoinResult.iloc[i]
            rp_data_df.loc[df_line.gid, self.rp_layer.geom] = df_line.geom

        ## 合并
        new_geom_df = pd.DataFrame(
            columns=["status", "geom", "ldbm", "joinstate"])
        for ldbm in list(rp_data_df[self.rp_layer.segment_code]):
            join_geo = None
            status = "OK"
            joinState = "NotJoin"
            try:
                segments_df = rp_data_df[rp_data_df[self.rp_layer.segment_code]
                                         == ldbm]
                if len(segments_df) > 1:
                    join_geo = shapely.ops.linemerge(
                        list(segments_df[self.rp_layer.geom]))
                    assert type(join_geo) == shapely.geometry.LineString
                    joinState = "Join"
            except AssertionError as assert_failure:
                print(str(assert_failure))
                status = "assertFail"
            except Exception as e:
                print(str(e))
                status = "other"
            geom_dict = {
                "status": status,
                "geom": join_geo,
                "ldbm": ldbm,
                "joinState": joinState
            }
            new_geom_df = new_geom_df.append(geom_dict, ignore_index=True)

        # 上传
        update_df = new_geom_df[new_geom_df.status == "OK"]
        update_df = update_df[update_df.joinState == "Join"]
        for i in range(len(update_df)):
            sql = f"update {target_table_name} set {columns[1]}=ST_GeomFromEWKT('SRID={settings.SRID};{update_df.iloc[i].geom.wkt}') where {self.rp_layer.segment_code}='{update_df.iloc[i].ldbm}' "
            conn.cursor().execute(sql)
        conn.commit()

        # 添加length col
        sql = f"""alter table {target_table_name} add column length numeric;
        update {target_table_name} set length= st_length(geom);
        """
        conn.cursor().execute(sql)
        conn.commit()
        print(f"表{target_table_name}存储完成，请查看数据库。")
        return new_geom_df
        pass

    def Restore_JoinResult_To_File(self):
        self.JoinResult.to_file(
            f"{self.result_path}/JoinResult_{self.layer}.shp")
        pass